﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Printing;
using ICSharpCode.SharpZipLib.Zip;

namespace ZipSpace
{
    public partial class MainPage : UserControl
    {
        BackgroundWorker backgroundWorker;
        string fileName;
        List<ZipEntryView> items = null;
        long totalSize = 0;
        long totalCompressedSize = 0;

        public MainPage()
        {
            InitializeComponent();

            Application.Current.InstallStateChanged += OnInstallStateChanged;
            Application.Current.CheckAndDownloadUpdateCompleted += OnCheckAndDownloadUpdateCompleted;

            this.UpdateInstallButton();

            if (App.Current.InstallState == InstallState.Installed)
            {
                InstallButton.Visibility = Visibility.Collapsed;

                App.Current.CheckAndDownloadUpdateAsync();
            }

            backgroundWorker = new BackgroundWorker();
            backgroundWorker.WorkerReportsProgress = true;
            backgroundWorker.WorkerSupportsCancellation = true;
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
            backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);

            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        private void UpdateInstallButton()
        {
            switch (App.Current.InstallState)
            {
                case InstallState.Installing:
                    InstallButton.IsEnabled = false;
                    InstallTextBlock.Text = "Installing application...";
                    break;

                case InstallState.Installed:
                    InstallButton.IsEnabled = false;
                    InstallButton.Visibility = Visibility.Visible;
                    InstallTextBlock.Text = "Installation complete";
                    break;

                case InstallState.NotInstalled:
                    InstallButton.IsEnabled = true;
                    InstallTextBlock.Text = "Install application";
                    break;

                case InstallState.InstallFailed:
                    InstallButton.IsEnabled = true;
                    InstallTextBlock.Text = "Installation failed";
                    break;
            }
        }

        private void Open()
        {
            OpenFileDialog dialog = new OpenFileDialog();

            dialog.Multiselect = false;
            dialog.Filter = "All Files (*.*)|*.*|Zip Files (*.zip)|*.zip|Silverlight XAP Files (*.xap)|*.xap|Open Office XML Files (*.docx, *.xlsx, *.pptx)|*.docx;*.xlsx;*.pptx";

            if (!(dialog.ShowDialog() ?? false))
                return;

            this.Open(dialog.File);
        }

        private void Open(FileInfo file)
        {
            try
            {
                fileName = file.Name;
            }
            catch (Exception ex)
            {
                fileName = "";
                Debug.WriteLine(ex);
            }

            this.Open(file.OpenRead());
        }

        private void Open(Stream stream)
        {
            this.DisableControls();

            backgroundWorker.RunWorkerAsync(stream);
        }

        private void EnableControls()
        {
            this.OpenButton.IsEnabled = true;
            this.treeMap.IsEnabled = true;
            this.dataGrid.IsEnabled = true;
        }

        private void DisableControls()
        {
            this.OpenButton.IsEnabled = false;
            this.treeMap.IsEnabled = false;
            this.dataGrid.IsEnabled = false;

            this.treeMap.DataContext = null;
            this.dataGrid.DataContext = null;
        }

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                fileName = App.Current.Resources["fileName"] as string;
                if (string.IsNullOrEmpty(fileName))
                    return;

                this.DisableControls();

                this.FileTextBlock.Text = string.Format("Downloading '{0}'", fileName);

                WebClient client = new WebClient();
                client.OpenReadCompleted += (s, args) =>
                    {
                        if (args.Cancelled)
                        {
                            this.EnableControls();
                            return;
                        }

                        if (args.Error != null)
                        {
                            this.OnException(this, new ExceptionEventArgs(args.Error));
                            this.EnableControls();
                            return;
                        }

                        backgroundWorker.RunWorkerAsync(args.Result);
                    };

                client.OpenReadAsync(new Uri(fileName));
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
                this.EnableControls();
            }
        }

        private void OnException(object sender, ExceptionEventArgs e)
        {
            if (!this.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(new EventHandler<ExceptionEventArgs>(this.OnException), sender, e);
                return;
            }

            ExceptionWindow.Show(e.Exception);
        }

        private void ZipTreeMap_ItemSelected(object sender, ItemEventArgs<ZipEntryView> e)
        {
            try
            {
                this.dataGrid.SelectedItem = e.Item;

                this.dataGrid.ScrollIntoView(e.Item, this.dataGrid.NameColumn);
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            try
            {
                foreach (object item in e.RemovedItems)
                {
                    ZipEntryView view = item as ZipEntryView;
                    if (view != null && view.IsSelected)
                        view.IsSelected = false;
                }

                long selectedSize = 0;
                long selectedCompressedSize = 0;

                foreach (object item in this.dataGrid.SelectedItems)
                {
                    ZipEntryView view = item as ZipEntryView;
                    if (view == null)
                        continue;

                    if (!view.IsSelected)
                        view.IsSelected = true;

                    selectedSize += view.Size;
                    selectedCompressedSize += view.CompressedSize;
                }

                int count = this.dataGrid.SelectedItems.Count;

                if (count < 1)
                {
                    if (this.items != null)
                        count = this.items.Count;

                    this.statusText.Text = string.Format("{0} file{1}, Size: {2}, Compressed Size: {3}", count, count == 1 ? "" : "s", ZipEntryView.FormatFileSize(totalSize), ZipEntryView.FormatFileSize(totalCompressedSize));
                }
                else
                {
                    this.statusText.Text = string.Format("{0} file{1} selected, Size: {2}, Compressed Size: {3}", count, count == 1 ? "" : "s", ZipEntryView.FormatFileSize(selectedSize), ZipEntryView.FormatFileSize(selectedCompressedSize));
                }
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void OpenButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                this.Open();
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.items = new List<ZipEntryView>();
            IEnumerable<GroupView> groups = null;

            try
            {
                this.FileTextBlock.Text = fileName;

                if (e.Cancelled)
                    return;

                if (e.Error != null)
                {
                    ExceptionWindow.Show(e.Error);
                    return;
                }

                this.openInstructions.Visibility = System.Windows.Visibility.Collapsed;

                groups = e.Result as IEnumerable<GroupView>;
                if (groups != null)
                {
                    foreach (var group in groups)
                    {
                        items.AddRange(group.Items);
                    }
                }

                totalCompressedSize = items.Sum(z => z.CompressedSize);
                totalSize = items.Sum(z => z.Size);
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
            finally
            {
                this.dataGrid.DataContext = items;
                this.treeMap.DataContext = groups;

                this.OpenButton.IsEnabled = true;
                this.treeMap.IsEnabled = true;
                this.dataGrid.IsEnabled = true;
            }
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            if (!(e.Argument is Stream))
                throw new InvalidOperationException("No stream was supplied for opening.");

            using (Stream stream = e.Argument as Stream)
            {
                e.Result = this.LoadZipFile(stream);
                return;
            }
        }

        private IEnumerable LoadZipFile(Stream stream)
        {
            ZipFile zipFile = new ZipFile(stream);

            List<ZipEntryView> items = new List<ZipEntryView>();

            int index = 1;

            Dictionary<string, GroupView> groups = new Dictionary<string, GroupView>();

            foreach (ZipEntry zipEntry in zipFile)
            {
                int percentProgress = (int)(((float)index / (float)zipFile.Count) * 100);

                backgroundWorker.ReportProgress(percentProgress, zipEntry.Name);

                Thread.Sleep(1);

                ZipEntryView zipEntryView = new ZipEntryView(zipEntry);

                GroupView groupView = null;
                if (groups.ContainsKey(zipEntryView.Path))
                {
                    groupView = groups[zipEntryView.Path];
                }
                else
                {
                    groupView = new GroupView()
                    {
                        Name = zipEntryView.Path,
                        Items = new List<ZipEntryView>(),
                    };
                    groups.Add(groupView.Name, groupView);
                }

                groupView.Size += zipEntryView.CompressedSize;
                groupView.Items.Add(zipEntryView);

                index++;
            }

            return groups.Values.AsEnumerable();
        }

        private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            try
            {
                this.FileTextBlock.Text = string.Format("{0}% done reading zip file '{1}', entry {2}.", e.ProgressPercentage, fileName, e.UserState);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        private void InstallButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                Application.Current.Install();
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void OnCheckAndDownloadUpdateCompleted(object sender, CheckAndDownloadUpdateCompletedEventArgs e)
        {
            try
            {
                if (e != null && e.UpdateAvailable)
                    OutOfBrowserNotification.Visibility = System.Windows.Visibility.Visible;
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void OnInstallStateChanged(object sender, EventArgs e)
        {
            try
            {
                UpdateInstallButton();
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void OnCloseUpdateNotification(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            try
            {
                OutOfBrowserNotification.Visibility = System.Windows.Visibility.Collapsed;
            }
            catch (Exception ex)
            {
                ExceptionWindow.Show(ex);
            }
        }

        private void OnDrop(object sender, DragEventArgs e)
        {
            try
            {
                if (!e.Data.GetDataPresent(DataFormats.FileDrop))
                    return;

                FileInfo[] files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
                if (files == null || files.Length < 1)
                    return;

                this.Open(files[0]);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }

        private void PrintButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                PrintDocument printDocument = new PrintDocument();

                bool printedTreeMap = false;
                bool printedGrid = false;

                string documentName = string.Format("ZipSpace - {0}", this.fileName);

                printDocument.PrintPage += (s, args) =>
                    {
                        if (!printedTreeMap)
                        {
                            printedTreeMap = true;
                            ZipTreeMap zipTreeMap = new ZipTreeMap();
                            zipTreeMap.DataContext = this.treeMap.DataContext;
                            args.PageVisual = zipTreeMap;
                            args.HasMorePages = true;
                        }
                        else if (!printedGrid)
                        {
                            printedGrid = true;
                            ZipDataGrid zipDataGrid = new ZipDataGrid();
                            zipDataGrid.DataContext = this.dataGrid.DataContext;
                            args.PageVisual = zipDataGrid;

                        }
                    };

                printDocument.Print(documentName);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }
    }
}
